﻿using Hylasoft.Opc.Common;
using Opc.Ua;
using SD.AOP.Core.Mediators;
using SD.AOP.Core.Models.Entities;
using SD.Common;
using SD.OpcProxy.OpcService.Base;
using SD.OpcProxy.OpcService.Extensions;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Transactions;

namespace SD.OpcProxy.OpcService.Proxy
{
    /// <summary>
    /// 代理变量
    /// </summary>
    public class ProxyVariable : BaseDataVariableState
    {
        /// <summary>
        /// 创建代理变量构造器
        /// </summary>
        /// <param name="opcServerUrl">OPC服务器地址</param>
        /// <param name="variableNo">变量编号</param>
        /// <param name="variableName">变量名称</param>
        /// <param name="unit">单位</param>
        /// <param name="isReadonly">是否只读</param>
        /// <param name="keepRecord">是否记录历史</param>
        /// <param name="description">描述</param>
        /// <param name="valueDescriptions">值描述字典</param>
        /// <param name="variableGroup">变量组</param>
        public ProxyVariable(string opcServerUrl, string variableNo, string variableName, string unit, bool isReadonly, bool keepRecord, string description, IDictionary<string, string> valueDescriptions, ProxyVariableGroup variableGroup)
            : base(variableGroup)
        {
            this.OpcServerUrl = opcServerUrl;
            this.Number = variableNo;
            this.Name = variableName;
            this.Unit = unit;
            this.IsReadonly = isReadonly;
            this.KeepRecord = keepRecord;
            this.ValueDescriptions = valueDescriptions ?? new Dictionary<string, string>();
            base.SymbolicName = variableNo;
            base.ReferenceTypeId = ReferenceTypes.Organizes;
            base.TypeDefinitionId = VariableTypeIds.BaseDataVariableType;
            base.NodeId = new NodeId($"{variableGroup.SymbolicName}.{variableNo}");
            base.BrowseName = new QualifiedName(variableNo);
            base.DisplayName = new LocalizedText(variableNo);
            base.Description = new LocalizedText(description);
            base.WriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description;
            base.UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description;
            base.DataType = DataTypeIds.BaseDataType;
            base.ValueRank = ValueRanks.Scalar;
            base.AccessLevel = AccessLevels.CurrentReadOrWrite;
            base.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
            base.Historizing = false;
            base.Value = null;
            base.StatusCode = StatusCodes.Good;
            base.Timestamp = DateTime.Now;

            variableGroup.AddChild(this);

            //值写入事件
            this.OnWriteValue = this.WriteValue;
        }

        /// <summary>
        /// OPC服务器地址
        /// </summary>
        public string OpcServerUrl { get; set; }

        /// <summary>
        /// 变量编号
        /// </summary>
        public string Number { get; set; }

        /// <summary>
        /// 变量名称
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 单位
        /// </summary>
        public string Unit { get; set; }

        /// <summary>
        /// 是否只读
        /// </summary>
        public bool IsReadonly { get; set; }

        /// <summary>
        /// 是否记录历史
        /// </summary>
        public bool KeepRecord { get; set; }

        /// <summary>
        /// 值描述字典
        /// </summary>
        /// <remarks>键：变量值，值：变量值描述</remarks>
        public IDictionary<string, string> ValueDescriptions { get; set; }

        /// <summary>
        /// 代理变量组
        /// </summary>
        public ProxyVariableGroup ProxyVariableGroup
        {
            get { return (ProxyVariableGroup)this.Parent; }
        }

        /// <summary>
        /// 赋值
        /// </summary>
        /// <param name="value">值</param>
        public void SetValue(object value)
        {
            base.Value = value;
            base.Timestamp = DateTime.Now;
            base.ClearChangeMasks(OpcServiceHost.NodeManager.SystemContext, false);
        }

        /// <summary>
        /// 值写入事件
        /// </summary>
        private ServiceResult WriteValue(ISystemContext context, NodeState node, NumericRange indexRange, QualifiedName dataEncoding, ref object value, ref StatusCode statusCode, ref DateTime timestamp)
        {
            try
            {
                if (node is ProxyVariable proxyVariable)
                {
                    #region # 验证

                    if (proxyVariable.IsReadonly)
                    {
                        throw new InvalidOperationException($"变量\"{proxyVariable.Number}\"是只读的，不可写入值！");
                    }

                    #endregion

                    string url = this.OpcServerUrl;
                    if (OpcProxyExtension.ClientVisitor.ContainsKey(url) && OpcProxyExtension.ClientVisitor[url].OpcClient.Status == OpcStatus.Connected)
                    {
                        IOpcClient opcClient = OpcProxyExtension.ClientVisitor[url].OpcClient;
                        opcClient.Write(proxyVariable.Number, value);
                    }
                    else
                    {
                        throw new InvalidOperationException($"变量\"{proxyVariable.Number}\"值写入失败，服务器\"{url}\"当前不可用！");
                    }
                }

                return new ServiceResult(statusCode);
            }
            catch (Exception exception)
            {
                #region # 日志处理

                ExceptionLog exceptionLog = new ExceptionLog
                {
                    ExceptionMessage = $"OPC变量写入值异常：{exception.Message}",
                    ExceptionInfo = exception.ToString(),
                    InnerException = exception.InnerException?.ToString(),
                    ExceptionType = exception.GetType().FullName,
                    ArgsJson = "[]",
                    Namespace = this.GetType().Namespace,
                    ClassName = this.GetType().Name,
                    MethodName = MethodBase.GetCurrentMethod().Name,
                    MethodType = "实例",
                    OccurredTime = DateTime.Now,
                    IPAddress = CommonExtension.GetLocalIPAddress()
                };

                using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress))
                {
                    LogMediator.Write(exceptionLog);
                    scope.Complete();
                }

                #endregion

                return new ServiceResult(StatusCodes.Bad, exception);
            }
        }
    }
}
